/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.harmonyos.viewmodel;


import ohos.app.Context;

import java.lang.reflect.InvocationTargetException;


@SuppressWarnings("WeakerAccess")
public class ViewModelProvider {

    private static final String DEFAULT_KEY =
            "harmonyos.lifecycle.ViewModelProvider.DefaultKey";
    
    public interface Factory {
        
        <T extends ViewModel> T create( Class<T> modelClass);
    }
    
    abstract static class KeyedFactory implements Factory {
  
        
        public abstract <T extends ViewModel> T create( String key,
                                                        Class<T> modelClass);

        
        @Override
        public <T extends ViewModel> T create( Class<T> modelClass) {
            throw new UnsupportedOperationException("create(String, Class<?>) must be called on "
                    + "implementaions of KeyedFactory");
        }
    }

    private final Factory mFactory;
    private final ViewModelStore mViewModelStore;


    public ViewModelProvider(ViewModelStoreOwner owner, Factory factory) {
        this(owner.getViewModelStore(), factory);
    }


    public ViewModelProvider( ViewModelStore store,  Factory factory) {
        mFactory = factory;
        mViewModelStore = store;
    }

    
    public <T extends ViewModel> T get( Class<T> modelClass) {
        String canonicalName = modelClass.getCanonicalName();
        if (canonicalName == null) {
            throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
        }
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }
    
    
    public <T extends ViewModel> T get( String key,  Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key);

        if (modelClass.isInstance(viewModel)) {
            //noinspection unchecked
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }
        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
        } else {
            viewModel = (mFactory).create(modelClass);
        }
        mViewModelStore.put(key, viewModel);
        //noinspection unchecked
        return (T) viewModel;
    }
    
    public static class NewInstanceFactory implements Factory {

        @SuppressWarnings("ClassNewInstance")
        
        @Override
        public <T extends ViewModel> T create( Class<T> modelClass) {
            //noinspection TryWithIdenticalCatches
            try {
                return modelClass.newInstance();
            } catch (InstantiationException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException("Cannot create an instance of " + modelClass, e);
            }
        }
    }


    public static class HarmonyViewModelFactory extends NewInstanceFactory {

        private static HarmonyViewModelFactory sInstance;


        public static HarmonyViewModelFactory getInstance( Context application) {
            if (sInstance == null) {
                sInstance = new HarmonyViewModelFactory(application);
            }
            return sInstance;
        }

        private Context mApplication;


        public HarmonyViewModelFactory( Context application) {
            mApplication = application;
        }

        
        @Override
        public <T extends ViewModel> T create( Class<T> modelClass) {
            if (HarmonyViewModel.class.isAssignableFrom(modelClass)) {
                //noinspection TryWithIdenticalCatches
                try {
                    return modelClass.getConstructor(Context.class).newInstance(mApplication);
                } catch (NoSuchMethodException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (InstantiationException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                } catch (InvocationTargetException e) {
                    throw new RuntimeException("Cannot create an instance of " + modelClass, e);
                }
            }
            return super.create(modelClass);
        }
    }
}
